#include <ctype.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <endian.h>

#include "memonic.h"

#if BYTE_ORDER == BIG_ENDIAN
#define htoav(x) (((x) >> 8) | ((x) << 8))
#else
#define htoav(x) x
#endif

const struct memonic *
smtable(const char *str) {
	int i;
	for (i = 0; i < mtable_size; i++)
		if (strcmp(str, mtable[i].nam) == 0)
			return &mtable[i];
	return NULL;
}

int
eatspc(FILE *i) {
	int c;
	int r = 0;
	while ((c = fgetc(i)) != EOF && isspace(c))
		r += c == '\n';
	if (c != EOF)
		ungetc(c, i);
	return r;
}

uint16_t
mapbits(uint16_t v, uint16_t m) {
	uint16_t i;
	uint16_t n;
	uint16_t r = 0;
	n = m;
	for (i = 0; n != 0; i++)
		n &= n - 1;
	if (i == 0)
		return 0;
	i = 1 << (i - 1);
	for (n = 0x8000; i != 0; n >>= 1)
		if ((m & n) != 0) {
			if ((v & i) != 0)
				r |= n;
			i >>= 1;
		}
	return r;
}

int
getn(FILE *i) {
	int c;
	int r = 0;
	int s = 0;
	if ((c = fgetc(i)) == '-') {
		s = 1;
		c = fgetc(i);
	}
	if (c == '0') {
		if ((c = fgetc(i)) == 'x' || c == 'X')
			while ((c = fgetc(i)) >= '0' && c <= 'f') {
				if (c >= 'a')
					c -= 'a' - 'A';
				else if (c > 'F')
					break;
				if (c >= 'A')
					r = (r << 4) + c - 'A' + 10;
				else if (c <= '9')
					r = (r << 4) + c - '0';
				else
					break;
			}
		else
			while (c >= '0' && c <= '7') {
				r = (r << 3) + c - '0';
				c = fgetc(i);
			}
	} else
		while (c >= '0' && c <= '9') {
			r = r * 10 + c - '0';
			c = fgetc(i);
		}
	if (c != EOF)
		ungetc(c, i);
	return s ? -r : r;
}

int
fasm(FILE *i, FILE *o) {
	char str[6];
	int align = 0;
	int ln = 1;
	int si = 0;
	int c;
	while ((c = fgetc(i)) != EOF) {
		if (c >= '0' && c <= '9') { /* data */
			if (si != 0)
				goto err;
			ungetc(c, i);
			if ((align & 2) != 0) {	/* word */
				uint16_t v;
				v = getn(i) & 0xFFFF;
				v = htoav(v);
				fwrite(&v, 2, 1, o);
			} else {		/* octet */
				uint8_t v;
				v = getn(i) & 0xFF;
				fwrite(&c, 1, 1, o);
				align ^= 1;
			}
		} else if (c == '"') { /* string */
			while ((c = fgetc(i)) != EOF) {
				if (c == '"' && (c = fgetc(i)) != '"') {
					ungetc(c, i);
					break;
				}
				ln += c == '\n';
				align ^= 1;
				fputc(c, o);
			}
		} else if (c == ';') { /* comment */
			do c = fgetc(i);
			while (c != EOF && c != '\n');
			ln += c == '\n';
		} else if (isspace(c)) {
			const struct memonic *mem;
			uint16_t v;
			ln += c == '\n';
			ln += eatspc(i);
			if (si == 0)
				continue;
			str[si] = '\0';
			if ((mem = smtable(str)) == NULL)
				goto err;
			v = mem->value;
			si = 0;
			if (v == 0xFFFF) /* END */
				return 0;
			if (v == 0xFFFE) { /* WORDS */
				align ^= 2;
				continue;
			}
			if ((align & 1) != 0)
				putc(0, o);
			align = 0;
			if (v == 0x940E || v == 0x940C) {
				/* CALL & JMP */
				uint32_t n;
				uint16_t a0;
				uint16_t a1;
				n   = getn(i) & 0x3FFFFF;
				ln += eatspc(i);
				a0  = n >> 16;
				a1  = n & 0xFFFF;
				v  |= mapbits(a0, mem->arg0);
				v   = htoav(v);
				a1  = htoav(a1);
				fwrite(&v, 2, 1, o);
				fwrite(&a1, 2, 1, o);
				continue;
			}
			if (mem->arg0 != 0) {
				uint16_t a;
				a = getn(i) & 0xFFFF;
				ln += eatspc(i);
				if (mem->arg0 == 0x03FF) {
					v |= mapbits(a, 0x01F0);
					v |= mapbits(a, 0x020F);
				} else
					v |= mapbits(a, mem->arg0);
			}
			if (mem->arg1 != 0 && mem->arg1 != 0xFFFF) {
				uint16_t a;
				a  = getn(i) & 0xFFFF;
				ln += eatspc(i);
				v  |= mapbits(a, mem->arg1);
			}
			v = htoav(v);
			fwrite(&v, 2, 1, o);
			if (mem->arg1 == 0xFFFF) {
				v   = getn(i) & 0xFFFF;
				ln += eatspc(i);
				v   = htoav(v);
				fwrite(&v, 2, 1, o);
			}
		} else {
			if (si >= sizeof(str) - 1)
				goto err;
			if (c >= 'a' && c <= 'z')
				c -= 'a' - 'A';
			if (c < 'A' || c > 'Z')
				goto err;
			str[si++] = c;
		}
	}
	return 0;
err:
	if (c != EOF)
		ungetc(c, i);
	if (si != 0) {
		str[si] = '\0';
		fprintf(stderr, "Invalid operation %.6s\n", str);
	}
	return ln;
}

int
main(int argc, char **argv) {
	FILE *i;
	FILE *o;
	const char *onam = "a.out";
	argv++;
	if (*argv == NULL)
		return 66;
	if (strcmp(*argv, "-o") == 0)
		onam = *++argv;
	else if (strcmp(*argv, "--") == 0)
		argv++;
	if (onam == NULL)
		return 64;
	if ((o = fopen(onam, "w")) == NULL)
		return 73;
	while (*argv != NULL) {
		int ln;
		if ((i = fopen(*argv, "r")) == NULL)
			return 66;
		if ((ln = fasm(i, o)) != 0) {
			int c;
			while ((c = fgetc(i)) != EOF && c != '\n')
				fputc(c, stderr);
			fprintf(stderr, "\n^ %s:%d\n", *argv, ln);
			return 65;
		}
		fclose(i);
		argv++;
	}
	fclose(o);
	return 0;
}

